package ar.edu.itba.it.cg.grupo01.game.terrain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Observer;
import java.util.Properties;

import ar.edu.itba.it.cg.grupo01.game.GameEndingState;
import ar.edu.itba.it.cg.grupo01.game.GameMode;
import ar.edu.itba.it.cg.grupo01.game.sound.Pioneer;
import ar.edu.itba.it.cg.grupo01.game.sound.PioneerSoundSystem;
import ar.edu.itba.it.cg.grupo01.utils.Utils;
import ar.edu.itba.it.cg.grupo01.vehicle.Car;

import com.jme.image.Texture;
import com.jme.image.Texture.ApplyMode;
import com.jme.image.Texture.CombinerFunctionRGB;
import com.jme.image.Texture.CombinerOperandRGB;
import com.jme.image.Texture.CombinerScale;
import com.jme.image.Texture.CombinerSource;
import com.jme.image.Texture.MagnificationFilter;
import com.jme.image.Texture.MinificationFilter;
import com.jme.image.Texture.WrapMode;
import com.jme.light.DirectionalLight;
import com.jme.light.Light;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.RenderState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.CullState.Face;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jmex.physics.PhysicsSpace;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.FaultFractalHeightMap;

public class Circuit extends Node {

	private static final long serialVersionUID = 1L;
	private TerrainPage terrain;

	private Barrier barrier;

	private List<? extends Checkpoint> checkpoints;
	private int currentCheckpoint;

	private List<Observer> lapObservers;

	private Properties modeProperties;

	private Vector3f lastCheckpointVisited = null;

	private int currentLap;
	private Node treesNode;
	private Obstacles obstacles;
	private LightState lightState;
	private Collection<Light> lights;
	private Properties generalProperties;
	private float collideSpeed;

	public Vector3f getLastCheckPointVisited() {
		if (lastCheckpointVisited == null) {
			return Utils.getPosition(modeProperties
					.getProperty("car.initialPosition"));
		}

		return lastCheckpointVisited;
	}

	public Circuit(PhysicsSpace physicsSpace, GameMode gameMode,
			Properties generalProperties, Properties gameProperties,
			List<Observer> obs, LightState ls) {
		this.modeProperties = gameProperties;
		this.generalProperties = generalProperties;
		this.lapObservers = new ArrayList<Observer>(obs);
		this.lightState = ls;
		this.lights = new ArrayList<Light>();
		this.collideSpeed = Float.valueOf(generalProperties
				.getProperty("car.collide.speed"));

		createCircuit(physicsSpace, gameMode);

	}

	private void createCircuit(PhysicsSpace physicsSpace, GameMode gameMode) {
		final StaticPhysicsNode staticNode = physicsSpace.createStaticNode();
		TerrainPage terrain = createTerrain();
		staticNode.attachChild(terrain);
		staticNode.getLocalTranslation().set(0, -150, 0);
		this.attachChild(staticNode);
		staticNode.generatePhysicsGeometry();
		this.terrain = terrain;

		createTrees(physicsSpace);
		createFence(physicsSpace);
		createObstacles(physicsSpace);
		createCheckPoints(gameMode);
		createLights();
	}

	private void createCheckPoints(GameMode gameMode) {
		checkpoints = gameMode.createCheckpoints(modeProperties);

		currentCheckpoint = 0;
		lastCheckpointVisited = null;
		currentLap = 0;

		Checkpoint nextCheckPoint = checkpoints.get(0);
		this.attachChild(nextCheckPoint);
		nextCheckPoint.place();

	}

	private void showNextCheckpoint() {

		this.detachChild(checkpoints.get(currentCheckpoint));

		this.lastCheckpointVisited = checkpoints.get(currentCheckpoint)
				.getPosition();

		if (currentCheckpoint == checkpoints.size() - 1) {
			currentCheckpoint = 0;
		} else {
			if (currentCheckpoint == 0) {
				incrementLap();
			}
			currentCheckpoint++;
		}

		Checkpoint nextCheckPoint = checkpoints.get(currentCheckpoint);
		this.attachChild(nextCheckPoint);
		this.updateRenderState();
		nextCheckPoint.place();
	}

	private void incrementLap() {
		currentLap++;

		for (Observer observer : lapObservers) {
			observer.update(null, (Object) Integer.valueOf(currentLap));
		}

	}

	private TerrainPage createTerrain() {

		FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 16, 0,
				2, 0.93f, 30000);
		Vector3f terrainScale = new Vector3f(10, 1, 10);
		heightMap.setHeightScale(0.001f);

		TerrainPage page = new TerrainPage("Terrain", 33, heightMap.getSize(),
				terrainScale, heightMap.getHeightMap());

		page.setDetailTexture(1, 16);

		CullState cs = DisplaySystem.getDisplaySystem().getRenderer()
				.createCullState();
		cs.setCullFace(Face.Back);
		cs.setEnabled(true);
		page.setRenderState(cs);

		TextureState ts = DisplaySystem.getDisplaySystem().getRenderer()
				.createTextureState();
		ts.setEnabled(true);

		Texture circuit = TextureManager.loadTexture(Utils
				.getResourceURL(generalProperties.getProperty("circuit")),
				MinificationFilter.Trilinear, MagnificationFilter.Bilinear);
		ts.setTexture(circuit, 0);

		Texture detail = TextureManager.loadTexture(
				Utils.getResourceURL(generalProperties
						.getProperty("circuit.detail")),
				MinificationFilter.Trilinear, MagnificationFilter.Bilinear);
		ts.setTexture(detail, 1);
		detail.setWrap(WrapMode.Repeat);

		circuit.setApply(ApplyMode.Combine);
		circuit.setCombineFuncRGB(CombinerFunctionRGB.Modulate);
		circuit.setCombineSrc0RGB(CombinerSource.CurrentTexture);
		circuit.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
		circuit.setCombineSrc1RGB(CombinerSource.PrimaryColor);
		circuit.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
		circuit.setCombineScaleRGB(CombinerScale.One);

		detail.setApply(ApplyMode.Combine);
		detail.setCombineFuncRGB(CombinerFunctionRGB.AddSigned);
		detail.setCombineSrc0RGB(CombinerSource.CurrentTexture);
		detail.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
		detail.setCombineSrc1RGB(CombinerSource.Previous);
		detail.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
		detail.setCombineScaleRGB(CombinerScale.One);

		page.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
		page.setRenderState(ts);

		return page;
	}

	private void createTrees(PhysicsSpace physicsSpace) {

		treesNode = new Node("trees");

		int amount = Integer.valueOf(generalProperties
				.getProperty("tree.amount"));
		float offset = Float.valueOf(generalProperties
				.getProperty("tree.offset"));
		String treeImage = generalProperties.getProperty("tree.image");
		for (int i = 1; i <= amount; i++) {

			Vector3f pos = Utils.getPosition(generalProperties
					.getProperty("tree" + i + ".pos"));
			pos.y += offset;
			Tree tree = new Tree("tree" + i, physicsSpace, Utils
					.getResourceURL(treeImage));
			tree.setPosition(pos.x, pos.y, pos.z);
			treesNode.attachChild(tree);
			treesNode.updateRenderState();
			treesNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
		}

		treesNode.setIsCollidable(true);

		TextureState ts = DisplaySystem.getDisplaySystem().getRenderer()
				.createTextureState();
		ts.setEnabled(true);
		Texture t = TextureManager.loadTexture(treeImage,
				MinificationFilter.Trilinear, MagnificationFilter.Bilinear);

		ts.setTexture(t);
		treesNode.setRenderState(ts);

		terrain.attachChild(treesNode);
		terrain.updateRenderState();
		treesNode.updateRenderState();
		this.updateRenderState();

	}

	private void createLights() {

		// Create a Basic Directional Light
		DirectionalLight dl = new DirectionalLight();
		dl.setDirection(new Vector3f(0, -1, 0));
		dl
				.setDiffuse(new ColorRGBA(Float.valueOf(generalProperties
						.getProperty("light.ambient.r")), Float
						.valueOf(generalProperties
								.getProperty("light.ambient.g")), Float
						.valueOf(generalProperties
								.getProperty("light.ambient.b")), Float
						.valueOf(generalProperties
								.getProperty("light.ambient.a"))));
		dl.setAmbient(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f));
		dl.setEnabled(true);

		lightState.attach(dl);
		lightState.setTwoSidedLighting(true);

		int lightAmount = Integer.valueOf(generalProperties
				.getProperty("light.amount"));

		for (int i = 1; i <= lightAmount && i <= 5; i++) {
			Vector3f pos = Utils.getPosition(generalProperties
					.getProperty("light" + i + ".pos"));
			PointLight pl = new PointLight();
			pl.setDiffuse(new ColorRGBA(Float.valueOf(generalProperties
					.getProperty("light" + i + ".r")),
					Float.valueOf(generalProperties.getProperty("light" + i
							+ ".g")), Float.valueOf(generalProperties
							.getProperty("light" + i + ".b")), Float
							.valueOf(generalProperties.getProperty("light" + i
									+ ".a"))));

			// pl.setAmbient(new ColorRGBA(Float.valueOf(properties
			// .getProperty("light" + i + ".ambient.r")),
			// Float.valueOf(properties
			// .getProperty("light" + i + ".ambient.g")),
			// Float.valueOf(properties
			// .getProperty("light" + i + ".ambient.b")),
			// Float.valueOf(properties
			// .getProperty("light" + i + ".ambient.a"))));
			pl.setLocation(pos);
			pl.setAttenuate(true);
			pl.setLinear(0.01f);
			pl.setEnabled(true);
			lightState.attach(pl);
			lightState.setTwoSidedLighting(true);
			lights.add(pl);
		}

	}

	public void removeLights() {
		for (Light pl : lights) {
			lightState.detach(pl);
		}
	}

	private void createFence(PhysicsSpace physicsSpace) {

		Barrier barrier = new Barrier("barrier");

		// we will do a little 'tweaking' by hand to make it fit in the terrain
		// a bit better.
		barrier.setLocalScale(new Vector3f(80, 150, 80));

		barrier.setLocalTranslation(new Vector3f(0,
				terrain.getHeight(25, 25) + 10, 0));

		barrier.updateGeometricState(0, true);

		final StaticPhysicsNode staticNode = physicsSpace.createStaticNode();

		staticNode.attachChild(barrier);

		staticNode.getLocalTranslation().set(
				terrain.getWorldBound().getCenter().x
						- barrier.getWorldBound().getCenter().x,
				-150,
				terrain.getWorldBound().getCenter().z
						- barrier.getWorldBound().getCenter().z);

		this.attachChild(staticNode);

		staticNode.generatePhysicsGeometry();

		this.barrier = barrier;
	}

	public TerrainPage getTerrain() {
		return terrain;
	}

	public long updateCheckPoint(Car car, Pioneer audio, GameMode gameMode,
			long currentTime) {

		long score = 0L;

		if (currentCheckpoint == -1) {
			return score;
		}
		Checkpoint checkpoint = checkpoints.get(currentCheckpoint);

		if (checkpoint.hasCollision(car, true)) {
			score = checkpoint.getScore();
			showNextCheckpoint();
			audio.playEffect(PioneerSoundSystem.SMALLCHEER, false);
		}
		return gameMode.gameEnded(currentLap, currentTime, modeProperties) == GameEndingState.WIN ? 0
				: score;
	}

	public Checkpoint getCheckpoint(int i) {
		return checkpoints.get(i);
	}

	private void createObstacles(PhysicsSpace physicsSpace) {
		this.obstacles = new Obstacles(physicsSpace, generalProperties);
		this.attachChild(this.obstacles);
	}

	public void addObserver(Observer o) {
		lapObservers.add(o);
	}

	public Barrier getBarrier() {
		return barrier;
	}

	public Node getTrees() {
		return treesNode;
	}

	public boolean collidesWith(Car car) {
		return car.getLinearSpeed() > collideSpeed
				&& (barrier.hasCollision(car, true)
						|| treesNode.hasCollision(car, true) || obstacles
						.hasCollision(car, true));
	}

	public RenderState getLightState() {
		return lightState;
	}

	public void setProperties(Properties gameProperties, GameMode gameMode,
			PhysicsSpace physicsSpace) {

		this.modeProperties = gameProperties;

		if (currentCheckpoint != -1) {
			this.detachChild(checkpoints.get(currentCheckpoint));
			currentCheckpoint = -1;
		}
		createCheckPoints(gameMode);
		this.detachChild(treesNode);
		createTrees(physicsSpace);

	}

}
